package indi.zhifa.core.dataauth.handler;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import indi.zhifa.core.common.util.CommonUtil;
import indi.zhifa.core.common.util.PackageScanUtil;
import indi.zhifa.core.common.web.entity.exception.ServiceException;
import indi.zhifa.core.dataauth.entity.MpDataAuth;
import indi.zhifa.core.dataauth.entity.MybatisDataAuth;
import indi.zhifa.core.dataauth.entity.MybatisFunDataAuth;
import indi.zhifa.core.dataauth.sqlHelper.ISqlVarProvider;
import indi.zhifa.core.dataauth.sqlHelper.SqlAnalyseHolder;
import indi.zhifa.core.common.util.MyClassPathScanner;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.WithItem;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.annotation.MapperScans;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.*;

@Slf4j
@Component
public class ZfDataPermissionInterceptor extends DataPermissionInterceptor {

    Map<String, SqlAnalyseHolder> mpSqlAnalyseMap;
    Map<String, Map<String,SqlAnalyseHolder>> mybatisSqlAnalyseMap;
    private final ISqlVarProvider mSqlVarProvider;
    private final ApplicationContext mApplicationContext;
    ThreadLocal<Boolean> isOnDataPermission = new ThreadLocal<>();

    public ZfDataPermissionInterceptor(ISqlVarProvider pSqlVarProvider,
                                       ApplicationContext pApplicationContext) {
        mpSqlAnalyseMap = new HashMap<>();
        mybatisSqlAnalyseMap = new HashMap<>();
        mSqlVarProvider = pSqlVarProvider;
        mApplicationContext = pApplicationContext;
    }


    @PostConstruct
    public void init(){

        // 取得扫描包
        String className = mApplicationContext.getBeanNamesForAnnotation(SpringBootApplication.class)[0];
        Object mainBean = mApplicationContext.getBean(className);
        Class<?> mainClass = mainBean.getClass();
        MapperScan mapperScan = AnnotationUtils.findAnnotation(mainClass, MapperScan.class);
        MapperScans mapperScans = AnnotationUtils.findAnnotation(mainClass, MapperScans.class);
        List<String> basePackages = new ArrayList<>();
        if(null != mapperScan){
            basePackages.addAll(Arrays.asList(mapperScan.basePackages()));
        }
        if(null != mapperScans && null != mapperScans.value() && mapperScans.value().length > 0){
            Arrays.stream(mapperScans.value()).forEach(mpScan->{basePackages.addAll(Arrays.asList(mpScan.basePackages()));});
        }
        List<Class<?>> mapperClasses = PackageScanUtil.scanBasePackage(basePackages,MpDataAuth.class);
       for(Class<?> mapperClass : mapperClasses){
            MpDataAuth mpDataAuth = mapperClass.getAnnotation(MpDataAuth.class);
            if (null == mpDataAuth) { // 其实不会发生
                continue;
            }
            // 如果不是funDetermined，解析本注解
            if(!mpDataAuth.funDetermined()){
                MybatisDataAuth dataAuth = mpDataAuth.value();
                String authExpression = dataAuth.authExpression();
                SqlAnalyseHolder sqlAnalyseHolder = new SqlAnalyseHolder(mSqlVarProvider);
                sqlAnalyseHolder.analyse(authExpression);
                mpSqlAnalyseMap.put(mapperClass.getName(), sqlAnalyseHolder);
            }else{
                // 纯mybatis情况
                Method[] methods = mapperClass.getMethods();
                for(Method method : methods){
                    MybatisFunDataAuth mybatisFunDataAuth = method.getAnnotation(MybatisFunDataAuth.class);
                    MybatisDataAuth[] mybatisDataAuths = null;
                    if (null == mybatisFunDataAuth) {
                        MybatisDataAuth mybatisDataAuth = method.getAnnotation(MybatisDataAuth.class);
                        if(null == mybatisDataAuth){
                            continue;
                        }
                        mybatisDataAuths = new MybatisDataAuth[]{mybatisDataAuth};
                    }else{
                        mybatisDataAuths = mybatisFunDataAuth.value();
                    }

                    if(null == mybatisDataAuths || mybatisDataAuths.length == 0){
                        continue;
                    }
                    Map<String,SqlAnalyseHolder> sqlAnalyseMap = new HashMap<>();
                    for(MybatisDataAuth mybatisDataAuth : mybatisDataAuths){
                        String expression = mybatisDataAuth.authExpression();
                        String substitutionVar = mybatisDataAuth.substitutionVar();
                        SqlAnalyseHolder sqlAnalyseHolder = new SqlAnalyseHolder(mSqlVarProvider);
                        sqlAnalyseHolder.analyse(expression);
                        sqlAnalyseMap.put(substitutionVar, sqlAnalyseHolder);
                    }
                    mybatisSqlAnalyseMap.put(mapperClass.getName()+"."+method.getName(),sqlAnalyseMap);
                }
            }
        }
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        boolean hasAuth = false;
        String mapperFunId = (String)obj;
        String mapperId = mapperFunId.substring(0,mapperFunId.lastIndexOf("."));
        PlainSelect plainSelect = select.getPlainSelect();
        if(null == isOnDataPermission.get() || !isOnDataPermission.get()){
            isOnDataPermission.set(true);
            String whereSql = plainSelect.getWhere().toString();
            if(mpSqlAnalyseMap.containsKey(mapperId)){
                SqlAnalyseHolder sqlAnalyseHolder = mpSqlAnalyseMap.get(mapperId);
                String substituteSql = sqlAnalyseHolder.getSql();
                whereSql = whereSql +" AND "+ substituteSql;
                hasAuth = true;
            }else if(mybatisSqlAnalyseMap.containsKey(mapperFunId)){
                Map<String,SqlAnalyseHolder> sqlAnalyseMap = mybatisSqlAnalyseMap.get(mapperFunId);
                for(Map.Entry<String,SqlAnalyseHolder> entry : sqlAnalyseMap.entrySet()){
                    String key = entry.getKey();
                    SqlAnalyseHolder sqlAnalyseHolder = entry.getValue();
                    String substituteSql = sqlAnalyseHolder.getSql();
                    if(StringUtils.hasText(key)){
                        whereSql = whereSql.replace("$"+key, substituteSql);
                    }else{
                        whereSql = whereSql +" AND "+ substituteSql;
                    }
                }
                hasAuth = true;
            }
            if(hasAuth){
                Expression whereExpression;
                try {
                    whereExpression = CCJSqlParserUtil.parseCondExpression(whereSql);
                }catch (Exception e){
                    throw new ServiceException("转化sql时发生错误。"+e.getMessage());
                }
                plainSelect.setWhere(whereExpression);
            }
        }
        processSelectBody(select, mapperFunId);
        List<WithItem> withItemsList = select.getWithItemsList();
        if (!CollectionUtils.isEmpty(withItemsList)) {
            withItemsList.forEach(withItem -> processSelectBody(withItem, mapperFunId));
        }
        isOnDataPermission.remove();
    }

}
